#include <d3d12_command.h>
#include <d3d12_core.h>
#include <array>
#include <helper.h>

namespace d3d12
{
	Command::Command():
		frames{},
		frame_index{},
		fence{},
		fence_value{}
	{
		DXASSERT(device()->CreateFence(fence_value, D3D12_FENCE_FLAG_NONE, IIDPPV(fence)));
		OBJ_NAME(fence, L"CMD Fence");
		int create_index{};
		for (auto& item : frames)
		{
			DXASSERT(device()->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IIDPPV(item.allocator)));
			OBJ_NAME(item.allocator, L"CMD Allocator[%d]", create_index);
			DXASSERT(device()->CreateCommandList1(0u, D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_LIST_FLAG_NONE, IIDPPV(item.cmd_list)));
			OBJ_NAME(item.cmd_list, L"CMD List[%d]", create_index);
			item.local_fence_event = CreateEventA(nullptr, FALSE, FALSE, nullptr);
			++create_index;
		}
	}
	Command::~Command()
	{
		release(fence);
		for (auto& item : frames)
		{
			release(item.cmd_list);
			release(item.allocator);
			CloseHandle(item.local_fence_event);
		}
	}
	ID3D12GraphicsCommandListN* Command::Begin(ID3D12PipelineState* pip /* = nullptr*/)
	{
		auto& cur_frame{ frames[frame_index] };
		WaitFrame(cur_frame.local_fence_event, cur_frame.local_fence_value);
		cur_frame.allocator->Reset();
		cur_frame.cmd_list->Reset(cur_frame.allocator, pip);
		return cur_frame.cmd_list;
	}
	void Command::WaitFrame(HANDLE e, u64 fv)
	{
		if (this->fence->GetCompletedValue() < fv)
		{
			this->fence->SetEventOnCompletion(fv, e);
			WaitForSingleObject(e, INFINITE);
		}
	}
	void Command::Flush()
	{
		auto e{ CreateEventA(nullptr, FALSE, FALSE, nullptr) };
		assert(e != nullptr);
		WaitFrame(e, this->fence_value);
		CloseHandle(e);
	}
	void Command::End()
	{
		auto& cur_frame{ frames[frame_index] };
		++fence_value;
		cur_frame.cmd_list->Close();
		//std::array<ID3D12CommandList*, 1> cmd_lists{ cur_frame.cmd_list };
		get_direct_queue()->ExecuteCommandLists(1, (ID3D12CommandList**) & cur_frame.cmd_list);
		get_direct_queue()->Signal(this->fence, fence_value);
		++frame_index;
		frame_index &= COMMAND_FRAME_COUNT_1;
		cur_frame.local_fence_value = fence_value;
	}

	void d3d12::Command::SurfaceResize(Surface& sur, u32 width, u32 height)
	{
		assert(sur.createor == this);
		sur.viewport.Width = static_cast<float>(width);
		sur.viewport.Height = static_cast<float>(height);
		sur.scissor_rect.right = static_cast<long>(width);
		sur.scissor_rect.bottom = static_cast<long>(height);

		Flush();

		for (auto& item : sur.frames)
		{
			release(item.ds);
			release(item.rt);
		}

		DXASSERT(sur.swapchain->ResizeBuffers(COMMAND_FRAME_COUNT, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING));
		for (u32 i = 0; i < COMMAND_FRAME_COUNT; i++)
		{
			auto& frame{ sur.frames[i] };
			DXASSERT(sur.swapchain->GetBuffer(i, IIDPPV(frame.rt)));
			device()->CreateRenderTargetView(frame.rt, nullptr, frame.rt_handle.cpu);
		}

		sur.current_index = 0u;
	}

	void Command::SurfaceDestory(Surface& sur)
	{
		assert(sur.createor == this);
		for (auto& item : sur.frames)
		{
			release(item.rt);
			release(item.ds);
		}
		release(sur.swapchain);
	}

	void Command::SurfacePresentBegin(Surface& sur,ID3D12GraphicsCommandListN*list)
	{
		assert(sur.createor == this);
		DXASSERT(sur.swapchain->Present1(sur.sync_interval, sur.flag, &sur.parameters));
		++sur.current_index;
		sur.current_index &= COMMAND_FRAME_COUNT_1;

		auto cur_index = sur.swapchain->GetCurrentBackBufferIndex();

		auto& cur_frame{ sur.frames[cur_index] };

		d3dx::resource_barrier bar{};
		bar.add(cur_frame.rt, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
		bar.apply(list);

		static float timer{};
		static float backcolor[4]{};
		timer += 0.002f;
		backcolor[0] = sinf(timer) / 2.0f + 0.5f;
		backcolor[1] = cosf(timer) / 2.0f + 0.5f;

		list->OMSetRenderTargets(1u, &cur_frame.rt_handle.cpu, FALSE, nullptr);
		list->ClearRenderTargetView(cur_frame.rt_handle.cpu, backcolor, 0u, nullptr);
	}

	void Command::SurfacePresentEnd(Surface& sur)
	{
		assert(sur.createor == this);
		auto& cur_frame{ sur.frames[sur.current_index] };
		d3dx::resource_barrier bar{};
		bar.add(cur_frame.rt, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
		bar.apply(frames[frame_index].cmd_list);
	}
}